﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Routing;
using System.Web.UI;
using System.Threading;

namespace MvcPatch.Routing
{
    public class DomainRoute : RouteBase
    {
        private DomainParser m_domainParser;

        public RouteBase InnerRoute { get; private set; }

        public string Pattern { get; private set; }

        public RouteValueDictionary Defaults { get; private set; }

        public RouteValueDictionary Constraints { get; private set; }

        public DomainRoute(RouteBase innerRoute, string pattern, RouteValueDictionary defaults, RouteValueDictionary constraints)
        {
            this.InnerRoute = innerRoute;

            this.Pattern = pattern.EndsWith("/", StringComparison.OrdinalIgnoreCase) ?
                pattern.Substring(0, pattern.Length - 1) : pattern;

            this.Defaults = defaults ?? new RouteValueDictionary();
            this.Constraints = constraints ?? new RouteValueDictionary();
            this.m_domainParser = new DomainParser(this.Pattern);
        }

        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            // match path
            var routeData = this.InnerRoute.GetRouteData(httpContext);
            if (routeData == null) return null;

            // match domain
            var domainValues = this.m_domainParser.Match(httpContext.Request.Url);
            if (domainValues == null) return null;

            // check constraints
            if (!this.CheckConstraints(domainValues)) return null;

            // merge
            this.MergeRouteData(routeData.Values, domainValues);
            routeData.Route = this;

            return routeData;
        }

        private bool CheckConstraints(RouteValueDictionary domainValues)
        {
            foreach (var pair in this.Constraints)
            {
                var value = domainValues[pair.Key];
                if (value == null) continue;

                var str = value.ToString();
                var constraint = pair.Value.ToString();

                // Review: use regex?
                if (!String.Equals(str, constraint, StringComparison.OrdinalIgnoreCase))
                {
                    return false;
                }
            }

            return true;
        }

        private void MergeRouteData(RouteValueDictionary routeValues, RouteValueDictionary domainValues)
        {
            foreach (var pair in domainValues)
            {
                routeValues.Add(pair.Key, pair.Value);
            }

            foreach (var pair in this.Defaults)
            {
                if (!routeValues.ContainsKey(pair.Key))
                {
                    routeValues.Add(pair.Key, pair.Value);
                }
            }
        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            if (!this.CheckConstraints(values)) return null;

            var innerPath = this.GetInnerPath(requestContext, values);
            if (innerPath == null) return null;
            
            // bind domain
            var domain = this.m_domainParser.Bind(requestContext.RouteData.Values, values);
            if (domain == null) return null;
            
            // merge
            innerPath.Route = this;
            innerPath.VirtualPath = Merge(requestContext, domain, innerPath.VirtualPath);

            return innerPath;
        }

        private VirtualPathData GetInnerPath(RequestContext requestContext, RouteValueDictionary values)
        {
            var innerValues = new RouteValueDictionary();
            foreach (var pair in values)
            {
                if (!this.m_domainParser.Segments.Contains(pair.Key) &&
                    !this.Defaults.ContainsKey(pair.Key))
                {
                    innerValues.Add(pair.Key, pair.Value);
                }
            }

            return this.InnerRoute.GetVirtualPath(requestContext, innerValues);
        }

        private static string Merge(RequestContext requestContext, string domain, string virtualPath)
        {
            var url = requestContext.HttpContext.Request.Url;
            var currentDomain = url.Scheme + "://" + url.Host;
            if (currentDomain.Equals(domain, StringComparison.OrdinalIgnoreCase))
            {
                return virtualPath;
            }

            string appPath = requestContext.HttpContext.Request.ApplicationPath ?? string.Empty;
            if (!appPath.EndsWith("/", StringComparison.OrdinalIgnoreCase))
            {
                appPath += "/";
            }

            return domain + appPath + virtualPath;
        }
    }
}
